package com.zjny.util;

import cn.hutool.poi.excel.ExcelUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelReader;
import com.alibaba.excel.read.builder.ExcelReaderBuilder;
import com.alibaba.excel.read.metadata.ReadSheet;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.zjny.listener.ReadExcelListener;
import com.zjny.pojo.BaseExcel;

import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import java.util.Map;

/**
 * packageName com.zjny.util
 *
 * @author cui haida
 * @version JDK 8
 * 2025/6/18
 */
public class ExcelUtils extends ExcelUtil {
    // 私有构造方法
    private volatile static ExcelUtils instance;

    private ExcelUtils() {
    }

    /**
     * DCL模式获取实例
     *
     * @return 实例
     */
    public static ExcelUtils getInstance() {
        //判断是否是空 第一重判断
        if (null == instance) {
            //加锁防止并发
            synchronized (ExcelUtils.class) {
                // 二重判断
                if (null == instance) {
                    instance = new ExcelUtils();
                }
            }
        }
        return instance;
    }


    /**
     * 读取excel，没有头
     *
     * @param is    输入流
     * @param clazz 要转成实体类的clazz
     * @return 转换后集合
     */
    public static <T> List<T> simpleRead(InputStream is, Class<T> clazz) {
        return EasyExcel.read(is).head(clazz).autoCloseStream(false).sheet().doReadSync();
    }

    /**
     * 带头读取excel
     *
     * @param is    输入流
     * @param clazz 要转成实体类的clazz
     * @return 转换后集合
     */
    public static <T> List<T> simpleReadWithHeader(InputStream is, Class<T> clazz) {
        return EasyExcel.read(is).head(clazz).autoCloseStream(false).sheet().headRowNumber(1).doReadSync();
    }

    /**
     * 读取excel表头信息
     *
     * @param inputStream 输入流
     * @param clazz       要转成实体类的clazz
     * @return 表头信息
     */
    public static <T extends BaseExcel> List<Map<Integer, String>> headerRead(InputStream inputStream, Class<T> clazz) {
        // 声明监听器
        ReadExcelListener<T> dataListener = new ReadExcelListener<>();
        // 这里 需要指定读用哪个class去读，然后读取第一个sheet
        EasyExcel.read(inputStream, clazz, dataListener).sheet().doRead();
        // 返回读到的头信息
        return dataListener.getHeadMapList();
    }

    /**
     * 读取excel信息并将其转换为指定实体类的列表。
     * 此方法会读取excel文件中的所有sheet。
     *
     * @param inputStream 输入流，指向要读取的excel文件。
     * @param head        要转换成的实体类的Class对象。实体类需要继承BaseExcelDTO。
     * @param rowNumber   表头所在的行数，从1开始计数。
     * @param <T>         实体类的类型，该类型需继承自BaseExcelDTO。
     * @return 返回一个包含所有读取并转换后的实体类的列表。
     */
    public static <T extends BaseExcel> List<T> repeatedReadToAllSheet(InputStream inputStream, Class<T> head, Integer rowNumber) {
        // 创建监听器，用于收集读取的excel数据
        ReadExcelListener<T> dataListener = new ReadExcelListener<>();

        // 构建excel读取器，并配置读取设置
        ExcelReaderBuilder excelReaderBuilder = new ExcelReaderBuilder();
        excelReaderBuilder.file(inputStream); // 指定读取的文件为输入流
        if (head != null) {
            excelReaderBuilder.head(head); // 指定实体类的头部信息
        }
        excelReaderBuilder.registerReadListener(dataListener); // 注册数据监听器
        excelReaderBuilder.headRowNumber(rowNumber).doReadAll(); // 指定表头行数，并读取所有sheet

        return dataListener.getList(); // 返回监听器收集到的数据列表
    }

    /**
     * 根据给定的输入流、实体类、sheet编号和表头行数，读取指定sheet的数据。
     *
     * @param in            输入流，指向要读取的Excel文件
     * @param clazz         实体类的Class对象，用于映射Excel数据到Java对象
     * @param sheetNo       要读取的sheet编号，从0开始计数
     * @param headRowNumber 表头行数，即数据开始的行数
     * @return 读取到的数据列表，类型为泛型T，继承自BaseExcel类
     * @throws RuntimeException 如果读取过程中发生错误
     */
    public static <T extends BaseExcel> List<T> readSheetByNo(InputStream in, Class<T> clazz, int sheetNo, int headRowNumber) throws RuntimeException {
        return EasyExcel.read(in) //调用read方法
                .registerReadListener(new ReadExcelListener<>()) //注册自定义监听器，字段校验可以在监听器内实现
                .head(clazz) //对应导入的实体类
                .sheet(sheetNo) //导入数据的sheet页编号，0代表第一个sheet页，如果不填，则会导入所有sheet页的数据
                .headRowNumber(headRowNumber) //列表头行数，1代表列表头有1行，第二行开始为数据行
                .doReadSync(); //开始读Excel，返回一个List<T>集合，继续后续入库操作
    }


    /**
     * 从指定的Excel sheet中读取数据并转换为指定类型的列表。
     *
     * @param excelReader   excel读取器，用于读取Excel内容。
     * @param clazz         模板类型，指定读取数据转换的目标类型。
     * @param headRowNumber 表头所在行号，指定Excel中表头的行数。
     * @param sheet         要读取的sheet编号，从0开始计数。
     * @param <T>           继承自BaseExcelDTO的类型，指定读取数据的转换类型。
     * @return 返回读取并转换后的数据列表。
     */
    private static <T extends BaseExcel> List<T> readSheet(ExcelReader excelReader, Class<T> clazz, int headRowNumber, Integer sheet) {
        // 创建数据监听器，用于收集读取的数据
        ReadExcelListener<T> dataListener = new ReadExcelListener<>();

        // 构建Excel读取配置，包括指定读取的sheet、表头行数和数据监听器
        ReadSheet build = EasyExcel.readSheet(sheet).head(clazz).registerReadListener(dataListener).headRowNumber(headRowNumber).build();

        // 执行Excel读取
        excelReader.read(build);

        // 返回读取并转换后的数据列表
        return dataListener.getList();
    }

    /**
     * 将数据写入Excel文件。
     *
     * @param fileName   要写入的Excel文件名。
     * @param list       要写入的数据列表。
     * @param excelClazz Excel数据行的转换类型，用于将数据转换为Excel格式。
     */
    public static <T, V extends BaseExcel> void write(String fileName, List<T> list, Class<V> excelClazz) {
        EasyExcel.write(fileName, excelClazz)
                .sheet()
                .doWrite(list);
    }

    /**
     * 将数据写入Excel文件。
     *
     * @param sheetName  sheetName
     * @param list       要写入的数据列表。
     * @param excelClazz Excel数据行的转换类型，用于将数据转换为Excel格式。
     * @param outputStream 输出流，用于写入Excel文件。
     */
    public static <T, V extends BaseExcel> void write(String sheetName, List<T> list, Class<V> excelClazz, OutputStream outputStream) {
        EasyExcel.write(outputStream, excelClazz)
                .excelType(ExcelTypeEnum.XLSX)
                .sheet(sheetName)
                .doWrite(list);
    }
}